//
//  MTOverlayDataSourceSqlite.m
//  GoMap
//
//  Created by jsheriff on 12/21/10.
//  Copyright 2010 The MITRE Corporation. All rights reserved.
//

#import "MTOverlayDataSourceMapPack.h"
#import "MTOverlayTile.h"

#define TILE_SIZE 256.0

@implementation MTOverlayDataSourceMapPack

- (id)initWithDatabasePath:(NSString *)path {
	if (self = [super init]) {
		databaseLock = [[NSLock alloc] init];
		[databaseLock lock];
        databasePath = [path retain];
		if (sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK) {
			NSString *query = @"select name, value from preferences";
			sqlite3_stmt *statement;
			int resultPrepare = sqlite3_prepare_v2(database, [query UTF8String], -1, &statement, nil);
			if (resultPrepare == SQLITE_OK) {
				double upperLeftLatitude;
				double upperLeftLongitude;
				double lowerRightLatitude;
				double lowerRightLongitude;
				
				while (sqlite3_step(statement) == SQLITE_ROW) {
					const char* name = (const char*) sqlite3_column_text(statement, 0);
					if (strcmp(name, "map.coverage.topLeft.latitude") == 0) {
						upperLeftLatitude = sqlite3_column_double(statement, 1);
					}
					else if (strcmp(name, "map.coverage.topLeft.longitude") == 0) {
						upperLeftLongitude = sqlite3_column_double(statement, 1);
					}
					else if (strcmp(name, "map.coverage.bottomRight.latitude") == 0) {
						lowerRightLatitude = sqlite3_column_double(statement, 1);
					}
					else if (strcmp(name, "map.coverage.bottomRight.longitude") == 0) {
						lowerRightLongitude = sqlite3_column_double(statement, 1);
					}
				}
					
				CLLocationCoordinate2D upperLeftLocation;
				upperLeftLocation.latitude = upperLeftLatitude;
				upperLeftLocation.longitude = upperLeftLongitude;
					
				CLLocationCoordinate2D lowerRightLocation;
				lowerRightLocation.latitude = lowerRightLatitude;
				lowerRightLocation.longitude = lowerRightLongitude;
					
				MKMapPoint upperLeftPoint = MKMapPointForCoordinate(upperLeftLocation);
				MKMapPoint lowerRightPoint = MKMapPointForCoordinate(lowerRightLocation);
				boundingMapRect = MKMapRectMake(upperLeftPoint.x, upperLeftPoint.y, lowerRightPoint.x - upperLeftPoint.x, lowerRightPoint.y - upperLeftPoint.y);			
			}
			else {
				//
				NSLog(@"Unable to prepare statement");
			}

		}
		else {
			database = nil;
		}
		[databaseLock unlock];
    }
    return self;
	
}

- (NSInteger) zoomLevelForZoomScale:(MKZoomScale)zoomScale {
    double numTilesAt1_0 = MKMapSizeWorld.width / TILE_SIZE;
    NSInteger zoomLevelAt1_0 = log2(numTilesAt1_0);  // add 1 because the convention skips a virtual level with 1 tile.
    NSInteger zoomLevel = MAX(0, zoomLevelAt1_0 + floor(log2f(zoomScale) + 0.5));
    return zoomLevel;
}

uint64_t getTileKey(uint64_t zoom, uint64_t x, uint64_t y)
{
	uint64_t zoom64 = zoom & 0xFFLL; // 8bits, 256 levels
	uint64_t x64 = x & 0xFFFFFFFLL;  // 28 bits
	uint64_t y64 = y & 0xFFFFFFFLL;  // 28 bits
	
	uint64_t key = (zoom64 << 56) | (x64 << 28) | (y64 << 0);
	
	return key;
}

- (BOOL)hasTilesInMapRect:(MKMapRect)rect zoomScale:(MKZoomScale)scale {
	// Return YES if we have any tiles in the requested rect at the requested scale
	uint64_t z = [self zoomLevelForZoomScale:scale];
        
    uint64_t minX = floor((MKMapRectGetMinX(rect) * scale) / TILE_SIZE);
    uint64_t maxX = floor((MKMapRectGetMaxX(rect) * scale) / TILE_SIZE);
    uint64_t minY = floor((MKMapRectGetMinY(rect) * scale) / TILE_SIZE);
    uint64_t maxY = floor((MKMapRectGetMaxY(rect) * scale) / TILE_SIZE);
    
	[databaseLock lock];
	for (uint64_t x = minX; x <= maxX; x++) {
		for (uint64_t y = minY; y <= maxY; y++) {
			uint64_t tileKey = getTileKey(z, x, y);
			NSString *query = [[NSString alloc] initWithFormat:@"select TILEKEY from TILES where TILEKEY = %qu", tileKey];
			sqlite3_stmt *statement;
			if (sqlite3_prepare_v2(database, [query UTF8String], -1, &statement, nil) == SQLITE_OK) {
				if (sqlite3_step(statement) == SQLITE_ROW) {
					[query release];
					[databaseLock unlock];
					return YES; 
				}
			}
			[query release];
		}
	}
	[databaseLock unlock];
    return NO;
}

- (NSArray *)tilesInMapRect:(MKMapRect)rect zoomScale:(MKZoomScale)scale {
	NSInteger z = [self zoomLevelForZoomScale:scale];
        
    NSInteger minX = floor((MKMapRectGetMinX(rect) * scale) / TILE_SIZE);
    NSInteger maxX = floor((MKMapRectGetMaxX(rect) * scale) / TILE_SIZE);
    NSInteger minY = floor((MKMapRectGetMinY(rect) * scale) / TILE_SIZE);
    NSInteger maxY = floor((MKMapRectGetMaxY(rect) * scale) / TILE_SIZE);
    
    NSMutableArray *tiles = [NSMutableArray arrayWithCapacity:4];
    
	[databaseLock lock];
	for (uint64_t x = minX; x <= maxX; x++) {
		for (uint64_t y = minY; y <= maxY; y++) {
			uint64_t tileKey = getTileKey(z, x, y);
			NSString *query = [[NSString alloc] initWithFormat:@"select IMAGE from TILES where TILEKEY = %qu", tileKey];
			sqlite3_stmt *statement;
			if (sqlite3_prepare_v2(database, [query UTF8String], -1, &statement, nil) == SQLITE_OK) {
				while (sqlite3_step(statement) == SQLITE_ROW) {
					MKMapRect frame = MKMapRectMake((double)(x * TILE_SIZE) / scale,
													(double)(y * TILE_SIZE) / scale,
													TILE_SIZE / scale,
													TILE_SIZE / scale);
					
					NSData *imageData = [[NSData alloc] initWithBytes:sqlite3_column_blob(statement, 0) length:sqlite3_column_bytes(statement, 0)];
					UIImage *image = [[UIImage alloc] initWithData:imageData];
					MTOverlayTile *tile = [[MTOverlayTile alloc] initWithFrame:frame image:image];
					[tiles addObject:tile];
					[imageData release];
					[image release];
					[tile release];
				}
			}
			[query release];
		}
	}
	[databaseLock unlock];
    
    return tiles;
}

- (MKMapRect)boundingMapRect
{
    return boundingMapRect;
}

- (void)dealloc {
    [databaseLock release];
    [super dealloc];
}

@end
